package com.coder.rental.security;

import cn.hutool.core.util.StrUtil;
import com.coder.rental.utils.JwtUtils;
import com.coder.rental.utils.RedisUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;


import java.io.IOException;

/**
 * OncePerRequestFilter是Spring boot 提供的过滤器抽象类
 * 在Spring security应用广泛，可以用来过滤请求
 * 这个过滤器通常被用来继承实现并在每次请求时执行
 */
@Component
public class VerifyTokenFilter extends OncePerRequestFilter {
    @Value("${request.login-url}")
    private String loginUrl;
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private CustomerUserDetailsService customerUserDetailsService;
    @Resource
    private LoginFailHandler loginFailHandler;
    /**
     * 执行过滤操作的核心方法
     * 该方法重写自父类，用于处理进入过滤器链的请求
     * 主要功能是验证请求URL是否为登录URL，如果不是，则尝试验证请求中的Token
     * 如果Token验证失败，将调用登录失败处理逻辑
     *
     * @param request 代表客户端的请求，用于获取请求信息
     * @param response 用于向客户端发送响应
     * @param filterChain 过滤器链，用于将请求传递给下一个过滤器或目标资源
     * @throws ServletException 如果过滤过程中发生Servlet异常
     * @throws IOException 如果过滤过程中发生IO异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取请求的URL
        String url = request.getRequestURI();
        // 检查请求URL是否与配置的登录URL相同
        if (!StrUtil.equals(url,loginUrl)){
            try {
                // 如果不是登录URL，尝试验证请求中的Token
                validateToken(request,response);
            }catch (AuthenticationException e){
                // 如果Token验证失败，调用登录失败处理逻辑
                loginFailHandler.onAuthenticationFailure(request,response,e);
            }
        }
        // 继续执行过滤链中的下一个过滤器或目标资源
        doFilter(request,response,filterChain);
    }

    /**
     * 校验token的有效性
     *
     * 该方法从HTTP请求中获取token，验证其存在性、有效性，并解析以获取用户信息
     * 如果token不存在、过期或错误，将抛出相应的认证异常
     * 如果token有效，将根据解析出的用户名获取用户详细信息，并设置认证信息到SecurityContextHolder中
     *
     * @param request HTTP请求对象，用于获取请求头和参数中的token
     * @param response HTTP响应对象，暂未在方法中使用，但可能在异常处理中用到
     * @throws AuthenticationException 如果token验证失败，抛出此异常
     */
    private void validateToken(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //校验token
        String token = request.getHeader("token");
        if(StrUtil.isEmpty(token)){
            token = request.getParameter("token");
        }
        if (StrUtil.isEmpty(token)){
            throw new CustomerAuthenticationException("token为空");
        }
        //如果token存在，需要和redis中存的token去比较
        String tokenKey = "token:"+token;
        String tokenValue = redisUtils.get(tokenKey);
        if (StrUtil.isEmpty(tokenValue)){
            throw new CustomerAuthenticationException("token已过期");
        }
        if (!StrUtil.equals(token,tokenValue)){
            throw new CustomerAuthenticationException("token错误");
        }
        //解析token中的用户名
        String username = JwtUtils.parseToken(token)
                .getClaim("username").toString();
        if (StrUtil.isEmpty(username)){
            throw new CustomerAuthenticationException("token解析失败");
        }
        //根据用户名获取用户详细信息
        UserDetails userDetails = customerUserDetailsService.loadUserByUsername(username);
        if (userDetails==null){
            throw new CustomerAuthenticationException("用户不存在");
        }
        //创建并设置认证信息
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails,
                        null,userDetails.getAuthorities());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource()
                .buildDetails(request));
        //将认证信息设置到SecurityContextHolder环境中
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }
}
